
class FlightRecords {
  /** @type {Map<number, FlightItem[]>} 年份 - 记录[] */
  cache = new Map();
  /** 获取 year 最小数值, 0 为非法值, 表示 2012 年前的数据还没有获取 */
  minYear = 0;

  constructor(obj) {
    /** @type {function} */
    this.assetPath = obj.assetPath;
  }

  /**
   * @param {number} year 
   * @param {boolean} renew 是否强制刷新
   * @returns {Promise<FlightItem[]>}
   */
  load(year, renew = false) {
    if (!renew && this.cache.get(year) != null) {
      return Promise.resolve(this.cache.get(year));
    }

    if (this.minYear != 0 && year < this.minYear) {
      return Promise.resolve([]);
    }

    if (year <= 2012) {
      return this.loadPre2012(renew)
        .then(items => items.filter(item => item.year === year));
    }

    let path = this.assetPath(`data/cn_${year}.json`);
    return fetch(path)
      .then(res => (res.ok) ? res.json() : Promise.reject(path))
      .then(data => {
        let items = this.convert(year, data);
        this.cache.set(year, items);
        return items;
      });
  }

  /**
   * 获取 2012 年及之前的数据, 它们在一个文件里面, 就是 cn_pre_2012.json
   * @param {boolean} renew 是否强制刷新
   * @returns {Promise<FlightItem[]>}
   */
  loadPre2012(renew = false) {
    /** @type {FlightItem[]} */
    let results = [];

    if (!renew && this.minYear > 0) {
      this.cache.forEach((items, year) => {
        if (year <= 2012) {
          results = results.concat(items);
        }
      });
      return Promise.resolve(results);
    }

    let path = this.assetPath('data/cn_pre_2012.json');
    return fetch(path)
      .then(res => (res.ok) ? res.json() : Promise.reject(path))
      .then(data => {
        /** @type {{year:number,list:{}[]}[]} */ let years = data.years;
        /** @type {Map<number, FlightItem[]>} 年份 - 记录[] */ let itemMap = new Map();

        this.minYear = years.reduce((p, c) => c.year < p ? c.year : p, 2012);
        years.forEach(content => {
          let items = this.convert(content.year, content);
          itemMap.set(content.year, items);
          results = results.concat(items);
        });

        // 这里优化一下, 所有没有发射任务的年份, 至少存一个空数组进去, 防止以后再来 loadPre2012 读取
        for (let year = this.minYear; year <= 2012; year++) {
          let items = itemMap.get(year);
          this.cache.set(year, items == null ? [] : items);
        }
        return results;
      });
  }

  /**
   * @returns {FlightItem[]}
   */
  convert(year, data) {
    if (data == null) {
      return [];
    }

    /** @type {any[]} */
    let list = data.list;
    return list.map( it => {
      let item = new FlightItem();
      item.year = year;
      for (let key in it) {
        switch (key) {
          case 'm': item.month = parseInt(it.m); break;
          case 'd': item.day = parseInt(it.d); break;
          case 's': item.site = it.s; break;
          case 'time': {
            // TODO 可能还要处理 timeZone
            item.time = it.time;
          } break;
          case 'o': item.orbit = it.o; break;
          case 'op': item.orbitParam = it.op; break;
          case 'v': {
            let vs = it.v;
            for (let index = 0; index + 1 < vs.length; index += 2) {
              let name = vs[index];
              let id = vs[index + 1];
              item.vehicles.push(toVehicle(name, id));
            }
          } break;
          case 'p': item.payloads = it.p; break;
          case 'pcount': item.payloadCount = parseInt(it.pcount); break;
          case 'ms': item.mass = it.ms; break;
          case 'suc': item.success = it.suc; break;
          case 'f': item.flag = parseInt(it.f); break;
          case 't': item.tags = it.t; break;
          case 'cospar': item.cospar = it.cospar; break;
          case 'desc': item.desc = it.desc; break;
        }
      }
      return item;
    }).sort((a, b) => a.compareTo(b));
  }

  cleanCache() {
    this.cache.clear();
  }

  /**
   * 最近的发射记录
   * @param {number} offset
   * @param {number} limit
   * @param {(item:FlightItem) => boolean} filter
   * @returns {Promise<FlightItem[]>}
   */
  getLatestRecords(offset, limit, filter = (_) => true) {
    let startYear = new Date().getFullYear();
    let results = [];
    return this.getRecords(offset + limit, startYear, results, filter)
        .then(_ => results.slice(offset, offset + limit).reverse());
  }

  /**
   * @param {(item:FlightItem) => boolean} filter
   *   如果这个数据是被需要的, 这个 function 需要返回 true
   */
  getRecords(limit, year, result, filter) {
    if (result.length >= limit) {
      return Promise.resolve(result);
    }
    if (this.minYear != 0 && year < this.minYear) {
      return Promise.resolve(result);
    }

    return this.load(year)
      .then(items => items.filter(filter))
      .then(items => items.slice().reverse())
      .then(items => {
        result.push(...items);
        return this.getRecords(limit, year - 1, result, filter);
      });
  }
}

class FlightItem {
  year = 1970;
  month = 4;
  day = 24;
  time = ""; // 可能为空字符串
  /** @type {Vehicle[]} */
  vehicles = []; // new Vehicle()
  orbit = "LEO"; // LEO SSO GTO GEO...
  orbitParam = ""; // 450KM 42° ...
  payloads = ["东方红一号"];
  payloadCount = 0; // 载荷数量
  mass = ""; // ~2.5T  0.125T+
  site = "酒泉";
  success = true; // T: true  F: false  H: 部分成功
  flag = 0; // 较为重要的任务为 0x1 位为 1,
            // 载人任务 0x2 位为 1
            // 部分成功 0x10 位为 1
  /** @type {string[]} */ tags = [];
  /** @type {Object | null} */ desc = null;
  cospar = "";
  isImportant() {
    return this.flag & 1;
  }
  isManned() {
    return this.flag & 0x2;
  }
  isPartialSuccess() {
    return this.flag & 0x10;
  }
  /**
   * 如果 this > that (this 比 that 晚), 返回正数.
   * @param {FlightItem} that 
   */
  compareTo(that) {
    let c = this.year != that.year ? this.year - that.year :
      this.month != that.month ? this.month - that.month :
      this.day != that.day ? this.day - that.day : this.time.localeCompare(that.time);
    return c == 0 ? this.cospar.localeCompare(that.cospar) : c;
  }
}

class Vehicle {
  constructor(name = "CZ-1", id = "Y1") {
    this.name = name;
    this.id = id;
  }
}

function toVehicle(name, id) {
  let vehicle = new Vehicle(name, id);
  return vehicle;
}

export {
  FlightRecords, FlightItem
};